
#ifndef TOKZRGENTR_PARSING_REGEX_PARSER_H
#define TOKZRGENTR_PARSING_REGEX_PARSER_H

#include"syntax_trees.h"
#include"u16string_ref.h"
#include"utils.h"
//#	author: @Mapupcal
//#	a parser for regular expression.
//#	supported syntax as follows.
//#		char_set	a or [a-z] or [a-zA-Z] ect.
//#		repeat		+ * ? {3} {3,8} {3,} {,8} ect.
//#		cater		ab [a-z][A-Z] ect.
//#		alter		a|b [a-z]|[A-Z] ect.
//#		escape		\d->[0-9] \w->[a-z] \s->[ ] \W->[A-Z] \[ -> [ 

namespace TokzrGentr
{
	using regex_tok_tag = enum class regex_tok_tag;
	using regex_tok = struct regex_tok;
	using regex_tokenizer = class regex_tokenizer;
	using regex_parser = class regex_parser;

	enum class regex_tok_tag
	{
		CHAR_SET,
		REPEAT,
		ALTER_SYM,
		ESCAPE,
		LEFT_PAREN,
		RIGHT_PAREN,
		END_OF_TOKS
	};

	struct regex_tok
	{
	private:
		regex_tok_tag _tag;
		u16string_ref _str;
	public:
		regex_tok() 
		:_tag(regex_tok_tag::END_OF_TOKS),_str(u""){}
		regex_tok(
			regex_tok_tag tag,
			u16string_ref string_ref
		)
			:_tag(tag), _str(string_ref) {}
		regex_tok_tag tag()
		{
			return _tag;
		}
		u16string_ref str_ref()
		{
			return _str;
		}
	};

	class regex_tokenizer
	{
	private:
		u16string_ref _regex_str;
		size_t _current_index;
		bool _is_fail;
		regex_tok _next_tok;
	private:
		inline regex_tok __next_tok()
		{
			if (_current_index == _regex_str.length())
				return	regex_tok{ regex_tok_tag::END_OF_TOKS,u16string_ref(u"") };

			regex_tok tok;

			switch (_regex_str[_current_index])
			{
			case u'[':
			{
				size_t end_tok_index = _current_index + 1;
				while (
					(end_tok_index < _regex_str.length())&&
					(_regex_str[end_tok_index] != u']') 
					)
				{
					end_tok_index++;
				}
				if (end_tok_index >= _regex_str.length())
				{
					_is_fail = true;
					break;
				}
				tok = regex_tok{ 
					regex_tok_tag::CHAR_SET,
					_regex_str.substr_ref(_current_index,end_tok_index + 1) 
				};
				_current_index = end_tok_index + 1;
				break;
			}
			case u'+':
			case u'*':
			case u'?':
				tok = regex_tok{
					regex_tok_tag::REPEAT,
					_regex_str.substr_ref(_current_index,_current_index + 1)
				};
				_current_index++;
				break;
			case u'{':
			{
				size_t end_tok_index = _current_index + 1;
				while ((_regex_str[end_tok_index] != u'}') &&
					end_tok_index < _regex_str.length())
				{
					end_tok_index++;
				}
				if (end_tok_index >= _regex_str.length())
				{
					_is_fail = true;
				}
				tok = regex_tok{
					regex_tok_tag::REPEAT,
					_regex_str.substr_ref(_current_index,end_tok_index + 1)
				};
				_current_index = end_tok_index + 1;
				break;
			}
			case u'|':
				tok = regex_tok{
					regex_tok_tag::ALTER_SYM,
					_regex_str.substr_ref(_current_index,_current_index + 1)
				};
				_current_index++;
				break;
			case u'\\'://escape
			{
				u16string_ref ch = _regex_str.substr_ref(_current_index + 1, _current_index + 2);
				tok = regex_tok{
					regex_tok_tag::ESCAPE,
					ch
				};
				_current_index += 2;
				break;
			}
			case u'(':
				tok = regex_tok{
					regex_tok_tag::LEFT_PAREN,
					_regex_str.substr_ref(_current_index,_current_index + 1)
				};
				_current_index++;
				break;
			case u')':
				tok = regex_tok{
					regex_tok_tag::RIGHT_PAREN,
					_regex_str.substr_ref(_current_index,_current_index + 1)
				};
				_current_index++;
				break;
			default:
				tok = regex_tok{
					regex_tok_tag::CHAR_SET,
					_regex_str.substr_ref(_current_index,_current_index + 1)
				};
				_current_index++;
				break;
			}

			return tok;
		}
	public:
		regex_tokenizer(const u16string_ref &str)
			:_regex_str(str),_current_index(0),_is_fail(false) {
			_next_tok = __next_tok();
		}
		void set_str(const u16string_ref &str)
		{
			_regex_str = str;
			_current_index = 0;
			_is_fail = false;
			_next_tok = __next_tok();
		}
		bool is_fail() {
			return _is_fail;
		}
		u16string_ref regex_str()
		{
			return _regex_str;
		}
		regex_tok next_tok()
		{
			if (_is_fail)
				return regex_tok();
			auto tok = _next_tok;
			_next_tok = __next_tok();
			return tok;
		}
	};

	class regex_parser
	{
	public:
		using size_type = size_t;
	private:

		inline void match(regex_tok_tag tag)
		{
			if (!_tokzr.is_fail() && _current_tok.tag() == tag)
				_is_fail = _is_fail || _tokzr.is_fail();
			else
				_is_fail = true;//# oops!syntax error occurred!
			_current_tok = _tokzr.next_tok();
		}

		inline void escape_2_char_set(const u16string_ref &str,std::shared_ptr<ucs2_ranges_set> sets_ptr)
		{
			auto ch = str[0];
			switch (ch)
			{
			case u'd':
				sets_ptr->add_range({ u'0',u'9' });
				break;
			case u's':
				sets_ptr->add_range(u' ');
				break;
			case u'w':
				sets_ptr->add_range({ u'a',u'z' });
				break;
			case u'W':
				sets_ptr->add_range({ u'A',u'Z' });
				break;
			case u'n':
				sets_ptr->add_range({ u'\n',u'\n' });
				break;
			default:
				sets_ptr->add_range(ch);
				break;
			}
		}

		inline std::shared_ptr<regex_node> analyze_char_set(const u16string_ref &str)
		{
			auto chars_set = std::make_shared<ucs2_ranges_set>(false);

			size_type index = 1;//#	Useless to scan [

			if (str.length() == 1)
			{
				chars_set->add_range(str[0]);
				chars_set->set_sign(true);
				return std::make_shared<char_set_node>(chars_set);
			}


			if (str[index] != u'^')
				chars_set->set_sign(true);
			else
				index++;//Useless to scan '^'

			auto end_index = str.length() - 1;
			while (index < end_index)
			{
				//#	test if it is a escape char
				auto ch = str [index];
				if (ch == u'\\')
				{
					escape_2_char_set(str.substr_ref(index + 1, index + 2), chars_set);
					index += 2;
					continue;
				}

				//#	single char
				if ((index < end_index) &&(str[index + 1] != u'-'))
				{
					chars_set->add_range(str[index]);
					index++;
					continue;
				}
				//#	range chars
				if (((index + 2) < end_index) && (str[index + 1] == u'-'))
				{
					chars_set->add_range({ str[index],str[index + 2] });
					index += 3;
					continue;
				}
				//#	single '-'
				chars_set->add_range(u'-');
				index++;
			}
			return std::make_shared<char_set_node>(chars_set);
		}

		inline std::shared_ptr<regex_node> analyze_escape(const u16string_ref& str)
		{
			//TODO:		#	buffer can help this function speed up.Fixme later
			std::shared_ptr<ucs2_ranges_set> sets = std::make_shared<ucs2_ranges_set>(true);
			escape_2_char_set(str, sets);
			return std::make_shared<char_set_node>(sets);
		}

		inline std::shared_ptr<regex_node> analyze_repeat(const u16string_ref &str, std::shared_ptr<regex_node> param)
		{
			auto ch = str[0];
			std::shared_ptr<regex_node> node;
			switch (ch)
			{
			case u'+':
				node = std::make_shared<repeat_node>(param, 1, 0, false);
				break;
			case u'*':
				node = std::make_shared<repeat_node>(param, 0, 0, false);
				break;
			case u'?':
				node = std::make_shared<repeat_node>(param, 0, 1, true);
				break;
			case u'{':
			{
				size_type index = 1;
				//#	scan for ','
				while (index != str.length() && str[index] != u',')
					index++;
				//#	for {n}
				if (index == str.length()) 
				{
					auto num = to_num(str.substr_ref(1, index - 1));
					node = std::make_shared<repeat_node>(param, num, num, true);
					break;
				}
				//#	for	{,n}
				if (index == 1)
				{
					auto num = to_num(str.substr_ref(2, str.length() - 1));
					node = std::make_shared<repeat_node>(param, 0, num, true);
					break;
				}
				//#	for {n,}
				if (index == str.length() - 2)
				{
					auto num = to_num(str.substr_ref(1, index));
					node = std::make_shared<repeat_node>(param, num, 0, false);
					break;
				}
				//# for {n,m}
				auto min = to_num(str.substr_ref(1, index));
				auto max = to_num(str.substr_ref(index + 1, str.length() - 1));
				node = std::make_shared<repeat_node>(param, min, max, true);
				break;
			}
			default:
				break;
			}
			return node;
		}

		std::shared_ptr<regex_node> parsing_tree()
		{
			if (_is_fail)
				return std::shared_ptr<regex_node>(nullptr);
			return re();
		}
		std::shared_ptr<regex_node> re()
		{
			return binary(unary(factor()));
		}
		std::shared_ptr<regex_node> cater(std::shared_ptr<regex_node> param)
		{
			auto fact = factor();
			auto un = unary(fact);
			auto node = std::make_shared<cater_node>(param, un);
			return binary(node);
		}
		std::shared_ptr<regex_node> factor()
		{
			std::shared_ptr<regex_node> node;
			switch (_current_tok.tag())
			{
			case regex_tok_tag::CHAR_SET:
			{
				auto str_ref = _current_tok.str_ref();
				node = analyze_char_set(str_ref);
				match(regex_tok_tag::CHAR_SET);
				break;
			}
			case regex_tok_tag::LEFT_PAREN:
				match(regex_tok_tag::LEFT_PAREN);
				node = re();
				match(regex_tok_tag::RIGHT_PAREN);
				break;
			case regex_tok_tag::ESCAPE:
			{
				auto str_ref = _current_tok.str_ref();
				node = analyze_escape(str_ref);
				match(regex_tok_tag::ESCAPE);
				break;
			}
			default:
				_is_fail = true;
				break;
			}
			return node;
		}
		std::shared_ptr<regex_node> unary(std::shared_ptr<regex_node> param)
		{
			std::shared_ptr<regex_node> node;
			switch (_current_tok.tag())
			{
			case regex_tok_tag::REPEAT:
			{
				auto str_ref = _current_tok.str_ref();
				node = analyze_repeat(str_ref,param);
				match(regex_tok_tag::REPEAT);
				break;
			}
			break;
			case regex_tok_tag::END_OF_TOKS:
			default:
				return param;
				break;
			}
			return unary(node);
		}
		std::shared_ptr<regex_node> binary(std::shared_ptr<regex_node> param)
		{
			std::shared_ptr<regex_node> node;
			switch (_current_tok.tag())
			{
			case regex_tok_tag::ALTER_SYM:
				match(regex_tok_tag::ALTER_SYM);
				node = std::make_shared<alter_node>(param, re());
				break;
			case regex_tok_tag::CHAR_SET:
			case regex_tok_tag::LEFT_PAREN:
			case regex_tok_tag::ESCAPE:
				node = cater(param);
				break;
			default:
				return param;
				break;
			}
			return node;
		}

		bool _is_fail;
		u16string_ref _regex_str;
		std::shared_ptr<regex_node> _tree_root;
		regex_tokenizer _tokzr;
		regex_tok _current_tok;
	public:
		regex_parser(const u16string_ref &str)
			:_regex_str(str), _is_fail(false),_tokzr(str) {
			_current_tok = _tokzr.next_tok();
		}
		std::shared_ptr<regex_node> tree_root() 
		{
			if (_tree_root.get())
				return _tree_root;
			_tree_root = parsing_tree();
			return _tree_root;
		}

		bool is_fail() const {
			return _is_fail;
		}
	};
}

#endif // !TOKZRGENTR_PARSING_REGEX_PARSER_H
